<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Golin安全扫描报告</title>
    <script>{{.ChartJS}}</script>
    <script>{{.ChartJSPlugin}}</script>
    <style>
        body {
            font-family: "Segoe UI", sans-serif;
            background: #f4f6f8;
            margin: 0;
            padding: 0;
        }
        header {
            background-color: #2c3e50;
            color: white;
            text-align: center;
            padding: 20px;
        }
        h1, h2 {
            margin: 0;
            padding-bottom: 10px;
        }
        .container {
            max-width: 1000px;
            margin: auto;
            padding: 20px;
        }
        .card {
            background: white;
            padding: 20px;
            margin-bottom: 20px;
            border-radius: 10px;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 10px;
        }
        th, td {
            border: 1px solid #ddd;
            padding: 8px;
            text-align: left;
        }
        th {
            background: #f0f0f0;
        }
        input[type="text"] {
            width: 100%;
            padding: 8px;
            margin: 10px 0;
            border: 1px solid #ccc;
            border-radius: 5px;
        }
        canvas {
            max-height: 250px;
        }
        .toc {
            position: fixed;
            top: 170px;
            right: 20px;
            width: 200px;
            background: #ecf0f1;
            padding: 15px;
            border-radius: 10px;
            box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
            z-index: 999;
        }
        .toc h3 {
            margin-top: 0;
            font-size: 16px;
        }
        .toc a {
            display: block;
            margin: 8px 0;
            color: #2c3e50;
            text-decoration: none;
            font-weight: bold;
        }
        .toc a:hover {
            text-decoration: underline;
            color: #2980b9;
        }
        .green-text { color: #2ecc71; }
        .red-text { color: red; }
        .modal {
            display: none;
            position: fixed;
            z-index: 9999;
            padding-top: 60px;
            left: 0; top: 0;
            width: 100%; height: 100%;
            overflow: auto;
            background-color: rgba(0,0,0,0.9);
        }
        .modal-content {
            margin: auto;
            display: block;
            max-width: 90%;
            max-height: 90%;
        }
        .close {
            position: absolute;
            top: 30px;
            right: 40px;
            color: white;
            font-size: 40px;
            font-weight: bold;
            cursor: pointer;
        }
        .screenshot-grid {
            display: flex;
            flex-wrap: wrap;
            gap: 15px;
        }

        .screenshot-item {
            width: 200px;
            text-align: center;
            font-size: 12px;
            color: #555;
        }

        .thumbnail {
            width: 100%;
            height: auto;
            border-radius: 6px;
            cursor: pointer;
            transition: transform 0.2s ease;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
        }

        .thumbnail:hover {
            transform: scale(1.05);
        }

        .filename {
            margin-top: 5px;
            word-break: break-all;
        }

        /* 模态框样式 */
        .modal {
            display: none;
            position: fixed;
            z-index: 9999;
            padding-top: 60px;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            overflow: auto;
            background-color: rgba(0,0,0,0.8);
        }

        .modal-content {
            margin: auto;
            display: block;
            max-width: 90%;
            max-height: 80%;
            border-radius: 8px;
            box-shadow: 0 2px 20px rgba(255,255,255,0.3);
        }

        .close {
            position: absolute;
            top: 30px;
            right: 50px;
            color: #fff;
            font-size: 35px;
            font-weight: bold;
            cursor: pointer;
        }
        .modal-nav {
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            font-size: 40px;
            color: white;
            cursor: pointer;
            padding: 10px;
            z-index: 10000;
            user-select: none;
        }

        .modal-prev {
            left: 30px;
        }

        .modal-next {
            right: 30px;
        }
        .card-row {
            display: flex;
            justify-content: space-between;
            flex-wrap: wrap;
            gap: 20px;
        }

        .card-row .card {
            flex: 1;
            min-width: 280px;
            max-width: 48%;
            box-sizing: border-box;
        }
        .green-text {
            color: #28a745;
            font-weight: bold;
        }

        .red-text {
            color: #dc3545;
            font-weight: bold;
        }
    </style>
</head>
<body>
<header>
    <h1>Golin安全扫描报告</h1>
    <p>生成时间：{{.Time}}</p>
</header>

<!-- 🧭 目录 -->
<div class="toc">
    <h3>📘 目录</h3>
    <a href="#stats">📊 基础统计</a>
    <a href="#chart1">📈 协议分布</a>
    <a href="#chart2">📊 主机与漏洞</a>
    <a href="#chart3">🍰 系统占比</a>
    <a href="#ports">📡 存活端口</a>
    <a href="#weakpass">🔐 弱口令</a>
    <a href="#vuln">🚨 漏洞信息</a>
    <a href="#screenshots">📸 截图展示</a>
    <a href="#ips">🌐 存活主机</a>
</div>

<div class="container">

    <div class="card" id="stats">
        <h2>任务信息</h2>
        <p>扫描时间：{{.Job.StartTime}}  -  {{.Job.EndTime}}</p>
        <p>扫描任务：{{.Job.IpJob}}，扫描端口：
            <span id="port-count"></span>
            <span id="port-list" style="display:none;"></span>
            <a href="javascript:void(0);" id="toggle-btn" onclick="togglePorts()">[展开]</a>
        </p>
        <p>是否启用口令扫描:
            {{if .Job.CrackJob}}
            <span class="green-text">是</span> 弱口令资产数：<span class="red-text">{{.Job.CrackCount}}</span>
            {{else}}
            <span class="red-text">否</span>
            {{end}}
        </p>
        <p>是否启用漏洞扫描:
            {{if .Job.PocJob}}
            <span class="green-text">是</span>，漏洞资产数：<span class="red-text">{{.Job.VulnerabilityCount}}</span>
            {{else}}
            <span class="red-text">否</span>
            {{end}}
        </p>

        <h2>基础统计</h2>
        <p>存活主机：<span class="green-text">{{.TotalHosts}}</span>，漏洞主机：<span class="red-text">{{.VulnHosts}}</span>，Linux：{{.LinuxCount}}，Windows：{{.WindowsCount}}，未识别: {{.UnidentifiedOS}}</p>
        <p>开放端口：<span class="green-text">{{.PortsCount}}</span>，SSH：{{.SSHCount}}，RDP：{{.RDPCount}}，Web：{{.WebCount}}，组件：{{.AppCount}}，数据库：{{.DBCount}}</p>
        <p>截图数量：<span class="green-text">{{.ScreenshotCount}}</span>，截图目录：{{.ScreenshotDir}}</p>
    </div>

    <div class="card-row">
        <div class="card" id="chart2">
            <h2>主机与漏洞</h2>
            <canvas id="hostVulnChart"></canvas>
        </div>
        <div class="card" id="chart4">
            <h2>高危端口分布</h2>
            <canvas id="portDangerChart"></canvas>
        </div>
    </div>

    <div class="card-row">
        <div class="card" id="chart1">
            <h2>协议分布图</h2>
            <canvas id="protocolChart" height="300"></canvas>
        </div>
        <div class="card" id="chart3">
            <h2>操作系统占比</h2>
            <canvas id="osPieChart"></canvas>
        </div>
    </div>


    <div class="card" id="ports">
        <h2>存活端口服务</h2>
        <input type="text" id="searchPorts" placeholder="搜索主机或端口...">
        <table id="portTable">
            <tr><th>#</th><th>主机</th><th>端口</th><th>组件</th></tr>
            {{range $i, $e := .PortServiceList}}
            <tr>
                <td>{{inc $i}}</td>
                <td>{{$e.Host}}</td>
                <td>{{$e.Port}}</td>
                <td>{{removeColor $e.Protocol}}</td>
            </tr>
            {{end}}
        </table>
    </div>

    <div class="card" id="weakpass">
        <h2>弱口令信息</h2>
        <table>
            <tr><th>主机</th><th>端口</th><th>用户</th><th>密码</th><th>模式</th></tr>
            {{range $key, $val := .CrackList}}
            <tr>
                <td>{{$key.Host}}</td>
                <td>{{$key.Port}}</td>
                <td>{{$val.User}}</td>
                <td>{{$val.Passwd}}</td>
                <td>{{$val.Mode}}</td>
            </tr>
            {{end}}
        </table>
    </div>

    <div class="card" id="vuln">
        <h2>漏洞信息</h2>
        <table>
            <tr><th>漏洞URL</th><th>漏洞特征文件</th><th>漏洞信息</th></tr>
            {{range .PocList}}
            <tr>
                <td><a href="{{.Url}}" target="_blank">{{.Url}}</a></td>
                <td>{{.Cve}}</td>
                <td>{{.Flag}}</td>
            </tr>
            {{end}}
        </table>
    </div>

    <div class="card" id="screenshots">
        <h2>📸 截图展示</h2>
        <div class="screenshot-grid">
            {{range .ScreenshotImages}}
            <div class="screenshot-item">
                <img src="../{{.}}" alt="{{.}}" class="thumbnail" data-src="../{{.}}" onclick="showModal(this)">
                <div class="filename">{{.}}</div>
            </div>
            {{end}}
        </div>
    </div>


    <div class="card" id="ips">
        <h2>存活主机列表</h2>
        <input type="text" id="searchOS" placeholder="搜索主机或操作系统及标签...">
        <table id="ipOS">
            <tr><th>#</th><th>IP 地址</th><th>操作系统</th><th>标签</th></tr>
            {{range $index, $host := .HostInfos}}
            <tr>
                <td>{{inc $index}}</td>
                <td>{{$host.IP}}</td>
                <td>{{$host.OS}}</td>
                <td>{{$host.Tag}}</td>
            </tr>
            {{end}}
        </table>
    </div>


</div>

<div id="modal" class="modal" onclick="hideModal()">
    <span class="close">&times;</span>
    <span class="modal-nav modal-prev" onclick="prevImage(event)">&#10094;</span> <!-- 左箭头 -->
    <img class="modal-content" id="modal-img">
    <span class="modal-nav modal-next" onclick="nextImage(event)">&#10095;</span> <!-- 右箭头 -->
</div>


<script>
    // 从模板中直接构造 JS 数组
    const rawProtocols = [
        {{range .PortServiceList}}"{{js .Protocol}}",{{end}}
    ];

    const protocols = rawProtocols.map(p => {
        // 去除 ANSI 转义颜色码
        p = p.replace(/\x1b\[[0-9;]*m/g, "");
        p = p.trim();

        // 如果包含 |，只保留 | 前的部分
        if (p.includes("|")) {
            p = p.split("|")[0].trim();
        }

        return p;
    });

    const protocolMap = {};
    protocols.forEach(p => {
        if (!p) return;
        protocolMap[p] = (protocolMap[p] || 0) + 1;
    });

    const labels = Object.keys(protocolMap);
    const data = Object.values(protocolMap);
    const backgroundColors = labels.map((_, i) =>
        ['#3498db', '#2ecc71', '#e67e22', '#9b59b6', '#f1c40f', '#34495e', '#1abc9c'][i % 7]
    );

    new Chart(document.getElementById('protocolChart'), {
        type: 'pie',
        data: {
            labels: labels,
            datasets: [{
                label: '协议分布',
                data: data,
                backgroundColor: backgroundColors
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                legend: { position: 'right' },
                datalabels: {
                    formatter: (value, ctx) => {
                        const label = ctx.chart.data.labels[ctx.dataIndex];
                        const total = ctx.chart.data.datasets[0].data.reduce((a, b) => a + b, 0);
                        const percent = ((value / total) * 100).toFixed(1) + '%';
                        return `${label}\n${value} (${percent})`;
                    },
                    color: '#fff',
                    font: {
                        weight: 'bold',
                        size: 12
                    }
                }
            }
        },
        plugins: [ChartDataLabels]
    });

    new Chart(document.getElementById('hostVulnChart'), {
        type: 'pie',
        data: {
            labels: ['安全主机', '漏洞主机'],
            datasets: [{
                label: '主机数量',
                data: [{{.TotalHosts}} - {{.VulnHosts}}, {{.VulnHosts}}],
    backgroundColor: ['#1abc9c', '#cb2110']
    }]
    },
    options: {
        responsive: true,
            maintainAspectRatio: false,
            plugins: {
            datalabels: {
                formatter: (value, ctx) => {
                    const label = ctx.chart.data.labels[ctx.dataIndex];
                    const total = ctx.chart.data.datasets[0].data.reduce((a, b) => a + b, 0);
                    const percent = ((value / total) * 100).toFixed(1) + '%';
                    return `${label}\n${value} (${percent})`;
                },
                    color: '#fff',
                    font: {
                    weight: 'bold',
                        size: 12
                }
            },
            legend: { position: 'right' }
        }
    },
    plugins: [ChartDataLabels]
    });

    new Chart(document.getElementById('osPieChart'), {
        type: 'pie',
        data: {
            labels: ['Linux', 'Windows', '未识别'],
            datasets: [{
                label: '操作系统',
                data: [{{.LinuxCount}}, {{.WindowsCount}}, {{.UnidentifiedOS}}],
    backgroundColor: ['#2ecc71', '#3498db', '#95a5a6']
    }]
    },
    options: {
        responsive: true,
            maintainAspectRatio: false,
            plugins: {
            datalabels: {
                formatter: (value, ctx) => {
                    const label = ctx.chart.data.labels[ctx.dataIndex];
                    const total = ctx.chart.data.datasets[0].data.reduce((a, b) => a + b, 0);
                    const percent = ((value / total) * 100).toFixed(1) + '%';
                    return `${label}\n${value} (${percent})`;
                },
                    color: '#fff',
                    font: {
                    weight: 'bold',
                        size: 12
                }
            },
            legend: { position: 'right' }
        }
    },
    plugins: [ChartDataLabels]
    });

    const rawPorts = [
        {{range .PortServiceList}}{{.Port}},{{end}}
    ];
    const dangerPorts = ['135', '136', '137', '138', '139', '445'];
    const dangerMap = {};
    rawPorts.forEach(p => {
        const port = String(p);
        if (dangerPorts.includes(port)) {
            dangerMap[port] = (dangerMap[port] || 0) + 1;
        }
    });
    const dangerLabels = Object.keys(dangerMap);
    const dangerData = Object.values(dangerMap);

    new Chart(document.getElementById('portDangerChart'), {
        type: 'pie',
        data: {
            labels: dangerLabels,
            datasets: [{
                label: '高危端口占比',
                data: dangerData,
                backgroundColor: ['#e74c3c', '#e67e22', '#f1c40f', '#9b59b6', '#3498db', '#2ecc71']
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                legend: { position: 'right' },
                datalabels: {
                    formatter: (value, ctx) => `${ctx.chart.data.labels[ctx.dataIndex]} (${value})`,
                    color: '#fff',
                    font: { weight: 'bold', size: 12 }
                }
            }
        },
        plugins: [ChartDataLabels]
    });

    function setupSearch(inputId, tableId) {
        const input = document.getElementById(inputId);
        const table = document.getElementById(tableId);
        input.addEventListener('keyup', () => {
            const filter = input.value.toLowerCase();
            const rows = table.getElementsByTagName('tr');
            for (let i = 1; i < rows.length; i++) {
                const cells = rows[i].getElementsByTagName('td');
                let match = false;
                for (let j = 0; j < cells.length; j++) {
                    if (cells[j].textContent.toLowerCase().includes(filter)) {
                        match = true;
                        break;
                    }
                }
                rows[i].style.display = match ? '' : 'none';
            }
        });
    }

    setupSearch('searchPorts', 'portTable');
    setupSearch('searchOS', 'ipOS');

    document.querySelectorAll(".screenshot-link").forEach(function(link) {
        link.addEventListener("click", function(e) {
            e.preventDefault();
            var modal = document.getElementById("imgModal");
            var modalImg = document.getElementById("modalImg");
            modalImg.src = link.getAttribute("data-src");
            modal.style.display = "block";
        });
    });

    document.querySelector(".close").addEventListener("click", function() {
        document.getElementById("imgModal").style.display = "none";
    });

    window.addEventListener("click", function(e) {
        var modal = document.getElementById("imgModal");
        if (e.target == modal) {
            modal.style.display = "none";
        }
    });

    function hideModal() {
        var modal = document.getElementById("modal");
        modal.style.display = "none";
    }
    let currentIndex = -1;
    let imageList = [];

    function showModal(imgElement) {
        const modal = document.getElementById("modal");
        const modalImg = document.getElementById("modal-img");

        if (imageList.length === 0) {
            // 只在第一次点击时初始化
            imageList = Array.from(document.querySelectorAll(".screenshot-item img")).map(img => img.getAttribute("data-src"));
        }

        const src = imgElement.getAttribute("data-src");
        currentIndex = imageList.indexOf(src);
        if (currentIndex === -1) currentIndex = 0;

        modal.style.display = "block";
        modalImg.src = imageList[currentIndex];
    }



    function prevImage(event) {
        event.stopPropagation();
        if (currentIndex > 0) {
            currentIndex--;
            document.getElementById("modal-img").src = imageList[currentIndex];
        }
    }

    function nextImage(event) {
        event.stopPropagation();
        if (currentIndex < imageList.length - 1) {
            currentIndex++;
            document.getElementById("modal-img").src = imageList[currentIndex];
        }
    }

    function formatUrl(raw) {
        let lastSlashIndex = raw.lastIndexOf('/');
        let s = lastSlashIndex !== -1 ? raw.substring(lastSlashIndex + 1) : raw;
        s = s.replace(/\.png$/, '');

        // 替换顺序：从最长的替代符到最短的，防止冲突
        s = s.replace(/___/g, '://');  // 协议
        s = s.replace(/__/g, '/');     // 路径
        s = s.replace(/_/g, ':');      // 端口或用户名密码中的冒号

        return s;
    }


    document.addEventListener("DOMContentLoaded", function() {
        const filenameDivs = document.querySelectorAll('.filename');
        filenameDivs.forEach(div => {
            const raw = div.textContent.trim();
            const url = formatUrl(raw);
            div.innerHTML = `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`;
        });
    });

    function togglePorts() {
        const portList = document.getElementById("port-list");
        const toggleBtn = document.getElementById("toggle-btn");

        if (portList.style.display === "none") {
            portList.style.display = "inline";
            toggleBtn.textContent = "[收起]";
        } else {
            portList.style.display = "none";
            toggleBtn.textContent = "[展开]";
        }
    }

    // 页面加载后，处理端口数量和内容
    document.addEventListener("DOMContentLoaded", function() {
        const raw = `{{.Job.PortJob}}`; // 例如: "8018,443,8080,888,800,8888"
        const ports = raw.split(",").filter(p => p.trim() !== "");
        const count = ports.length;

        document.getElementById("port-count").textContent = `共 ${count} 个端口`;
        document.getElementById("port-list").textContent = `（${ports.join(", ")}）`;
    });
</script>
</body>
</html>
